package rs.etf.mips.crossroad.logic.lightcontrol;

import rs.etf.mips.crossroad.logic.Crossroad;
import rs.etf.mips.crossroad.logic.Light;
import rs.etf.mips.crossroad.logic.Line;
import rs.etf.mips.crossroad.logic.Street;
import rs.etf.mips.crossroad.logic.StreetDirection;

/**
 * @author Momcilo
 * 
 */
public abstract class LightState {

	protected static int blinkRepetitions = 3;
	protected static int blinkRepetitionPeriod = 1000;// ms
	protected long maxTimeTaken = 7000;

	protected long timeTaken;
	/**
	 * @author Momcilo
	 * 
	 *         Determines group of streets - North, South; East, West
	 * 
	 */
	protected enum Direction {
		Vertical, Horizontal;

		private boolean arrived;

		public boolean isArrived() {
			return arrived;
		}

		public void setArrived(boolean arrived) {
			this.arrived = arrived;
		}

		private Direction() {
			this.arrived = false;
		}
	}

	/**
	 * every time the timer ticks, counter is decremented for tickPeriod
	 * milliseconds
	 */
	protected static long tickPeriod = 50;// ms

	/**
	 * the last yellowPeriod milliseconds the light is yellow
	 */
	protected static long yellowPeriod = 1000;// ms

	/**
	 * first redPeriod milliseconds the light is red - inertia for displaying
	 * the green light
	 */
	protected static long redPeriod = 1000;// ms

	protected long counter;
	protected long initCounter;

	protected Crossroad crossroad;

	LightState(Crossroad crossroad, long initCounter) {
		this.crossroad = crossroad;
		this.initCounter = initCounter;
		this.counter = this.initCounter;
	}

	/**
	 * @return number of blink repetitions
	 */
	public static int getBlinkRepetitios() {
		return blinkRepetitions;
	}

	/**
	 * sets the number of blink repetitions
	 * 
	 * @param blinkRepetitios
	 */
	public static void setBlinkRepetitios(int blinkRepetitios) {
		LightState.blinkRepetitions = blinkRepetitios;
	}

	public static int getBlinkRepetitionPeriod() {
		return blinkRepetitionPeriod;
	}

	public static void setBlinkRepetitionPeriod(int repetitionPeriod) {
		LightState.blinkRepetitionPeriod = repetitionPeriod;
	}

	public static long getTickPeriod() {
		return tickPeriod;
	}

	public static void setTickPeriod(long tickPeriod) {
		LightState.tickPeriod = tickPeriod;
	}

	public void tick() {
		this.counter -= LightState.tickPeriod;
		this.pedestrianTimeTick();
		updateCrossroadLights();
		updatePedestrianDisplay();
		if (this.counter <= 0) {
			resetCounter();
			LightState state = this.nextState();
			state.initializeState();
			this.crossroad.setLightControl(state);
		}
	}

	protected abstract void updateCrossroadLights();

	protected abstract LightState nextState();

	protected abstract void updatePedestrianDisplay();

	void resetCounter() {
		this.counter = this.initCounter;
	}

	protected void setCrossroadLight(Light light) {
		Street street;

		for (StreetDirection direction : StreetDirection.values()) {
			street = this.crossroad.getStreet(direction);
			this.setStreetLight(street, light);
		}
	}

	protected void setStreetLight(Street street, Light light) {
		for (int i = 3; i < Street.lineCount; i++) {
			setLineLight(street.getLine(i), light);
		}
	}

	protected void setLineLight(Line line, Light light) {
		line.setSemaphore(light);
	}

	protected abstract long timeGained();
	
	protected Light calculateLight() {

		Light light = Light.Off;

		if (this.counter <= 0
				|| this.counter >= this.initCounter + timeGained() - LightState.redPeriod) {
			light = Light.Red;
		} else if (this.counter <= yellowPeriod
				|| this.counter >= this.initCounter + timeGained() - LightState.redPeriod
						- LightState.yellowPeriod) {
			light = Light.Yellow;
		} else {
			boolean green = this.counter > LightState.blinkRepetitionPeriod
					* LightState.blinkRepetitions + yellowPeriod;
			for (int i = 0; !green && i < LightState.blinkRepetitions; i++) {
				if (this.counter > i * LightState.blinkRepetitionPeriod
						+ yellowPeriod
						&& this.counter < (i + 0.5)
								* LightState.blinkRepetitionPeriod
								+ yellowPeriod)
					green = true;
			}
			if (green) {
				light = Light.Green;
			}
		}

		return light;
	}

	private void pedestrianTimeTick() {
		Street street = this.crossroad.getStreet(StreetDirection.South);
		street.getPedestrianSemaphore().tick(LightState.tickPeriod);

		street = this.crossroad.getStreet(StreetDirection.East);
		street.getPedestrianSemaphore().tick(LightState.tickPeriod);

		street = this.crossroad.getStreet(StreetDirection.North);
		street.getPedestrianSemaphore().tick(LightState.tickPeriod);

		street = this.crossroad.getStreet(StreetDirection.West);
		street.getPedestrianSemaphore().tick(LightState.tickPeriod);
	}

	protected void refreshArrivals(Direction direction) {
		switch (direction) {
		case Horizontal:
			Direction.Horizontal.setArrived(this.crossroad.getStreet(
					StreetDirection.West).isPedestrianArrived()
					|| this.crossroad.getStreet(StreetDirection.East)
							.isPedestrianArrived());
			break;
		case Vertical:
			Direction.Vertical.setArrived(this.crossroad.getStreet(
					StreetDirection.South).isPedestrianArrived()
					|| this.crossroad.getStreet(StreetDirection.North)
							.isPedestrianArrived());
			break;
		default:
			break;
		}
	}

	protected void setArrivals(Direction direction, boolean arrived) {
		switch (direction) {
		case Horizontal:
			this.crossroad.getStreet(StreetDirection.West)
					.setPedestrianArrived(arrived);
			this.crossroad.getStreet(StreetDirection.East)
					.setPedestrianArrived(arrived);
			break;
		case Vertical:
			this.crossroad.getStreet(StreetDirection.North)
					.setPedestrianArrived(arrived);
			this.crossroad.getStreet(StreetDirection.South)
					.setPedestrianArrived(arrived);
			break;
		default:
			break;
		}
	}

	protected abstract void initializeState();
	
	/**
	 * @return <b>true</b> if the starting inertia period passed; otherwise
	 *         <b>false</b>.
	 */
	protected boolean isInertiaRedPeriodPassed() {
		return this.initCounter + timeGained() - redPeriod - yellowPeriod > this.counter;
	}

	/**
	 * Gets value that indicates whether the state is ending (blinking started)
	 * 
	 * @return <b>true</b> if the state is ending state; otherwise,
	 *         <b>false</b>.
	 */
	protected boolean isStateInFinalPhase() {
		return this.counter <= LightState.blinkRepetitionPeriod
				* LightState.blinkRepetitions + yellowPeriod;
	}

	protected long StreetCounter(Direction direction) {
		LightStateFactory factory = LightStateFactory.instance();
		switch (direction) {
		case Horizontal:
			return factory
					.getLightState(LightStateDescriptor.HorizontalForward).initCounter
					+ factory
							.getLightState(LightStateDescriptor.HorizontalLeftRight).initCounter
					+ factory
							.getLightState(LightStateDescriptor.HorizontalRightForward).initCounter;
		case Vertical:
			return factory.getLightState(LightStateDescriptor.VerticalForward).initCounter
					+ factory
							.getLightState(LightStateDescriptor.VerticalLeftRight).initCounter
					+ factory
							.getLightState(LightStateDescriptor.VerticalRightForward).initCounter;
		default:
			return 0;
		}
	}
}
